/* -*- Mode: C; indent-tabs-mode: f; c-basic-offset: 4; tab-width: 4 -*- */
/*
 * Copyright (C) 2005 Red Hat, Inc.
 *
 * Nemo is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * Nemo is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; see the file COPYING.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street - Suite 500,
 * Boston, MA 02110-1335, USA.
 *
 * Author: Alexander Larsson <alexl@redhat.com>
 *
 */

#include <config.h>
#include "nemo-query-editor.h"
#include "nemo-file-utilities.h"
#include "nemo-history-menu-item.h"

#include <string.h>
#include <glib/gi18n.h>
#include <gio/gio.h>

#include <eel/eel-glib-extensions.h>
#include <libnemo-private/nemo-global-preferences.h>
#include <libnemo-private/nemo-file.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>

typedef enum
{
    NEMO_QUERY_EDITOR_ROW_TYPE,
    NEMO_QUERY_EDITOR_ROW_MODIFIED,
    NEMO_QUERY_EDITOR_ROW_SIZE,

    NEMO_QUERY_EDITOR_ROW_LAST
} NemoQueryEditorRowType;

typedef struct
{
    NemoQueryEditorRowType type;
    NemoQueryEditor *editor;
    GtkWidget *hbox;
    GtkWidget *label;

    GtkWidget *type_widget;

    void *data;
} NemoQueryEditorRow;

typedef struct
{
    const char *name;
    GtkWidget *(*create_widgets)(NemoQueryEditorRow *row);
    void (*add_to_query)(NemoQueryEditorRow *row,
                         NemoQuery *query);
    void (*add_rows_from_query)(NemoQueryEditor *editor,
                                NemoQuery *query);
} NemoQueryEditorRowOps;

typedef struct
{
    GtkWidget *entry;
    GtkWidget *vbox;
    GtkWidget *search_current_button;
    GtkWidget *menu;

    gboolean change_frozen;
    guint typing_timeout_id;

    char *current_uri;

    GList *rows;
    char *last_set_query_text;

    gchar **histories;
} NemoQueryEditorPrivate;

struct _NemoQueryEditor
{
    GtkBox parent_object;

    NemoQueryEditorPrivate *priv;
};

G_DEFINE_TYPE_WITH_PRIVATE (NemoQueryEditor, nemo_query_editor, GTK_TYPE_BOX)

enum
{
    CHANGED,
    CANCEL,
    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

static void entry_activate_cb (GtkWidget *entry, NemoQueryEditor *editor);
static void entry_changed_cb  (GtkWidget *entry, NemoQueryEditor *editor);
static void nemo_query_editor_changed_force (NemoQueryEditor *editor,
                                             gboolean force);
static void nemo_query_editor_changed (NemoQueryEditor *editor);

static void on_saved_searches_setting_changed (GSettings *settings,
                                               gchar     *key,
                                               gpointer   user_data);

static NemoQueryEditorRow *nemo_query_editor_add_row (NemoQueryEditor *editor,
                                                      NemoQueryEditorRowType type);

static GtkWidget *type_row_create_widgets (NemoQueryEditorRow *row);
static void type_row_add_to_query (NemoQueryEditorRow *row,
                                   NemoQuery *query);
static void type_add_rows_from_query (NemoQueryEditor *editor,
                                      NemoQuery *query);

static GtkWidget *modified_date_row_create_widgets (NemoQueryEditorRow *row);
static void modified_date_row_add_to_query (NemoQueryEditorRow *row,
                                            NemoQuery *query);
static void modified_date_add_rows_from_query (NemoQueryEditor *editor,
                                               NemoQuery *query);

static GtkWidget *size_row_create_widgets (NemoQueryEditorRow *row);
static void size_row_add_to_query (NemoQueryEditorRow *row,
                                   NemoQuery *query);
static void size_add_rows_from_query (NemoQueryEditor *editor,
                                      NemoQuery *query);

static NemoQueryEditorRowOps row_type[] = {
    {N_("File Type:"),
     type_row_create_widgets,
     type_row_add_to_query,
     type_add_rows_from_query},
    {N_("Modified Date:"),
     modified_date_row_create_widgets,
     modified_date_row_add_to_query,
     modified_date_add_rows_from_query},
    {N_("File Size:"),
     size_row_create_widgets,
     size_row_add_to_query,
     size_add_rows_from_query},
};

static gchar *
get_sanitized_query_string (NemoQueryEditor *editor)
{
    const gchar *entry_text;
    gchar *ret;

    entry_text = gtk_entry_get_text (GTK_ENTRY (editor->priv->entry));

    ret = g_strdup (entry_text);
    ret = g_strstrip (ret);

    return ret;
}

static void
nemo_query_editor_finalize (GObject *object)
{
    NemoQueryEditor *editor;
    GList *l;
    NemoQueryEditorRow *row;

    editor = NEMO_QUERY_EDITOR (object);

    for (l = editor->priv->rows; l != NULL; l = l->next) {
        row = l->data;
        editor->priv->rows = g_list_remove (editor->priv->rows, row);
	    g_free (row);
    }
    g_list_free (editor->priv->rows);
}

static void
nemo_query_editor_dispose (GObject *object)
{
    NemoQueryEditor *editor;

    editor = NEMO_QUERY_EDITOR (object);

    g_clear_pointer (&editor->priv->last_set_query_text, g_free);

    if (editor->priv->typing_timeout_id > 0)
    {
        g_source_remove (editor->priv->typing_timeout_id);
        editor->priv->typing_timeout_id = 0;
    }

    g_clear_object (&editor->priv->menu);
    g_clear_pointer (&editor->priv->histories, g_strfreev);

    g_signal_handlers_disconnect_by_func (nemo_preferences,
                                          on_saved_searches_setting_changed,
                                          editor);

    G_OBJECT_CLASS (nemo_query_editor_parent_class)->dispose (object);
}

static void
nemo_query_editor_grab_focus (GtkWidget *widget)
{
    NemoQueryEditor *editor = NEMO_QUERY_EDITOR (widget);

    if (gtk_widget_get_visible (widget)) {
        gtk_entry_grab_focus_without_selecting (GTK_ENTRY (editor->priv->entry));
    }
}

static void
nemo_query_editor_class_init (NemoQueryEditorClass *class)
{
    GObjectClass *gobject_class;
    GtkWidgetClass *widget_class;
    GtkBindingSet *binding_set;

    gobject_class = G_OBJECT_CLASS (class);
    gobject_class->dispose = nemo_query_editor_dispose;
    gobject_class->finalize = nemo_query_editor_finalize;

    widget_class = GTK_WIDGET_CLASS (class);
    widget_class->grab_focus = nemo_query_editor_grab_focus;

    signals[CHANGED] = g_signal_new ("changed",
                                     G_TYPE_FROM_CLASS (class),
                                     G_SIGNAL_RUN_LAST,
                                     0,
                                     NULL, NULL, NULL,
                                     G_TYPE_NONE, 2, NEMO_TYPE_QUERY, G_TYPE_BOOLEAN);

    signals[CANCEL] = g_signal_new ("cancel",
                                    G_TYPE_FROM_CLASS (class),
                                    G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                                    0,
                                    NULL, NULL, NULL,
                                    G_TYPE_NONE, 0);

    binding_set = gtk_binding_set_by_class (class);
    gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "cancel", 0);
}

GFile *
nemo_query_editor_get_location (NemoQueryEditor *editor)
{
    GFile *file = NULL;
    if (editor->priv->current_uri != NULL)
        file = g_file_new_for_uri (editor->priv->current_uri);
    return file;
}

static void
on_saved_searches_setting_changed (GSettings *settings,
                                   gchar     *key,
                                   gpointer   user_data)
{
    NemoQueryEditor *editor;

    g_return_if_fail (NEMO_IS_QUERY_EDITOR (user_data));

    editor = NEMO_QUERY_EDITOR (user_data);

    g_clear_pointer (&editor->priv->histories, g_strfreev);
    editor->priv->histories = g_settings_get_strv (settings, key);
}

static void
add_key_to_histories (NemoQueryEditor *editor,
                      const gchar     *entry)
{
    gint i;
    GPtrArray *array;

    array = g_ptr_array_new ();

    g_ptr_array_add (array, g_strdup (entry));

    if (editor->priv->histories != NULL) {
        for (i = 0; i < g_strv_length (editor->priv->histories); i++) {
            if (g_strcmp0 (entry, editor->priv->histories[i]) != 0)
                g_ptr_array_add (array, g_strdup (editor->priv->histories[i]));
        }
    }

    g_ptr_array_add (array, NULL);

    g_signal_handlers_block_by_func (nemo_preferences,
                                     on_saved_searches_setting_changed,
                                     editor);

    g_settings_set_strv (nemo_preferences,
                         NEMO_PREFERENCES_SAVED_SEARCHES,
                         (const gchar * const *) array->pdata);

    g_signal_handlers_unblock_by_func (nemo_preferences,
                                       on_saved_searches_setting_changed,
                                       editor);

    g_clear_pointer (&editor->priv->histories, g_strfreev);
    editor->priv->histories = (gchar **) g_ptr_array_free (array, FALSE);
}

static void
remove_key_from_histories (NemoQueryEditor *editor,
                           const gchar     *entry)
{
    gint i;
    GPtrArray *array;

    array = g_ptr_array_new ();

    if (editor->priv->histories != NULL) {
        for (i = 0; i < g_strv_length (editor->priv->histories); i++) {
            if (g_strcmp0 (entry, editor->priv->histories[i]) != 0) {
                g_ptr_array_add (array, g_strdup (editor->priv->histories[i]));
            }
        }
    }

    g_ptr_array_add (array, NULL);

    g_signal_handlers_block_by_func (nemo_preferences,
                                     on_saved_searches_setting_changed,
                                     editor);

    g_settings_set_strv (nemo_preferences,
                         NEMO_PREFERENCES_SAVED_SEARCHES,
                         (const gchar * const *) array->pdata);

    g_signal_handlers_unblock_by_func (nemo_preferences,
                                       on_saved_searches_setting_changed,
                                       editor);

    g_clear_pointer (&editor->priv->histories, g_strfreev);
    editor->priv->histories = (gchar **) g_ptr_array_free (array, FALSE);
}

static void
entry_activate_cb (GtkWidget *entry, NemoQueryEditor *editor)
{
    g_autofree gchar *text = NULL;

    if (editor->priv->typing_timeout_id > 0) {
        g_source_remove (editor->priv->typing_timeout_id);
        editor->priv->typing_timeout_id = 0;
    }

    text = get_sanitized_query_string (editor);

    //搜索文字不能为空
    if (g_strcmp0 (text, "") == 0) {
        return;
    }

    add_key_to_histories (editor, text);

    nemo_query_editor_changed_force (editor, TRUE);
}

static gboolean
typing_timeout_cb (gpointer user_data)
{
    NemoQueryEditor *editor;

    editor = NEMO_QUERY_EDITOR (user_data);
    editor->priv->typing_timeout_id = 0;

    nemo_query_editor_changed (editor);

    return FALSE;
}

#define TYPING_TIMEOUT 250

static void
entry_changed_cb (GtkWidget *entry, NemoQueryEditor *editor)
{
    g_autofree gchar *text = NULL;

    if (editor->priv->change_frozen) {
        return;
    }

    if (editor->priv->typing_timeout_id > 0) {
        g_source_remove (editor->priv->typing_timeout_id);
        editor->priv->typing_timeout_id = 0;
    }

    text = get_sanitized_query_string (editor);

    //删除搜索文本时，更新搜索结果
    if (strlen (text) == 0)
    {
        editor->priv->typing_timeout_id = g_timeout_add (TYPING_TIMEOUT,
                                                         typing_timeout_cb,
                                                         editor);
    }
}

/* Type */

static gboolean
type_separator_func (GtkTreeModel *model,
                     GtkTreeIter *iter,
                     gpointer data)
{
    char *text;
    gboolean res;

    gtk_tree_model_get (model, iter, 0, &text, -1);

    res = text != NULL && strcmp (text, "---") == 0;

    g_free (text);
    return res;
}

struct
{
    char *name;
    char *mimetypes[35];
} mime_type_groups[] = {
    //文件夹
    {(char *)N_("Folders"),
     {(char *)"inode/directory",
      NULL}},
    //图片
    {(char *)N_("Picture"),
     {(char *)"application/vnd.oasis.opendocument.image",
      (char *)"application/x-krita",
      (char *)"image/bmp",
      (char *)"image/cgm",
      (char *)"image/gif",
      (char *)"image/jpeg",
      (char *)"image/jpeg2000",
      (char *)"image/png",
      (char *)"image/svg+xml",
      (char *)"image/tiff",
      (char *)"image/x-compressed-xcf",
      (char *)"image/x-pcx",
      (char *)"image/x-photo-cd",
      (char *)"image/x-psd",
      (char *)"image/x-tga",
      (char *)"image/x-xcf",
      NULL}},
    //音频
    {(char *)N_("Audio"),
     {(char *)"application/ogg",
      (char *)"application/x-cda",
      (char *)"application/x-smaf",
      (char *)"audio/x-vorbis+ogg",
      (char *)"audio/ac3",
      (char *)"audio/basic",
      (char *)"audio/midi",
      (char *)"audio/x-flac",
      (char *)"audio/mp4",
      (char *)"audio/mpeg",
      (char *)"audio/aac",
      (char *)"audio/AMR",
      (char *)"audio/mp2",
      (char *)"audio/flac",
      (char *)"audio/x-m4r",
      (char *)"audio/x-ape",
      (char *)"audio/x-aiff",
      (char *)"audio/x-wav",
      (char *)"audio/x-wavpack",
      (char *)"audio/x-ms-wma",
      (char *)"audio/x-mpeg",
      (char *)"audio/x-ms-asx",
      (char *)"audio/x-pn-realaudio",
      NULL}},
    //视频
    {(char *)N_("Video"),
     {(char *)"video/mp4",
      (char *)"video/3gpp",
      (char *)"video/mpeg",
      (char *)"video/quicktime",
      (char *)"video/vivo",
      (char *)"video/x-avi",
      (char *)"video/x-mng",
      (char *)"video/x-ms-asf",
      (char *)"video/x-ms-wmv",
      (char *)"video/x-msvideo",
      (char *)"video/x-nsv",
      (char *)"video/x-real-video",
      NULL}},
    //压缩文件
    {(char *)N_("Compressed Archives"),
     {(char *)"application/x-7z-compressed",
      (char *)"application/x-cdisplay",
      (char *)"application/x-cpio",
      (char *)"application/vnd.microsoft.portable-executable",
      (char *)"application/x-msdownload",
      (char *)"application/java-archive",
      (char *)"application/zip",
      (char *)"application/x-zip-compressed",
      (char *)"application/zip-compressed",
      (char *)"application/x-gtar",
      (char *)"application/x-bzip",
      (char *)"application/x-bzip2",
      (char *)"application/gzip",
      (char *)"application/x-xz",
      (char *)"application/x-compress",
      (char *)"application/x-rar-compressed",
      (char *)"application/vnd.rar",
      (char *)"application/x-compressed-tar",
      (char *)"application/x-tar",
      (char *)"application/x-tarz",
      (char *)"application/x-bzip-compressed-tar",
      (char *)"application/x-lzip-compressed-tar",
      (char *)"application/x-lzma-compressed-tar",
      (char *)"application/x-tzo",
      (char *)"application/x-lz4",
      (char *)"application/x-xz-compressed-tar",
      (char *)"application/x-webarchive",
      (char *)"application/x-java-archive",
      (char *)"application/vnd.comicbook+zip",
      (char *)"application/x-ms-dos-executable",
      (char *)"application/x-cd-image",
      NULL}},
    //程序
    {(char *)N_("Programs"),
     {(char *)"text/x-java-source",
      (char *)"text/javascript",
      (char *)"text/x-csrc",
      (char *)"text/x-c",
      (char *)"text/x-chdr",
      (char *)"text/x-h",
      (char *)"text/x-asm",
      (char *)"text/x-python",
      (char *)"text/x-java",
      (char *)"text/x-c++src",
      (char *)"text/x-c++hdr",
      (char *)"text/x-csharp",
      (char *)"text/x-objcsrc",
      (char *)"application/x-php",
      (char *)"application/x-archive",
      (char *)"application/javascript",
      (char *)"application/x-tgif",
      (char *)"application/x-object",
      (char *)"application/x-perl",
      (char *)"application/x-httpd-php",
      (char *)"application/x-shellscript",
      (char *)"application/vnd.sqlite3",
      (char *)"application/x-sqlite3",
      NULL}},
    //文本文档
    {(char *)N_("Text Documents"),
     {(char *)"text/plain",
      NULL}},
    //文本文件
    {(char *)N_("Text File"),
     {(char *)"application/rtf",
      (char *)"application/msword",
      (char *)"application/vnd.sun.xml.writer",
      (char *)"application/vnd.sun.xml.writer.global",
      (char *)"application/vnd.sun.xml.writer.template",
      (char *)"application/vnd.oasis.opendocument.text",
      (char *)"application/vnd.oasis.opendocument.text-template",
      (char *)"application/x-abiword",
      (char *)"application/x-applix-word",
      (char *)"application/x-mswrite",
      (char *)"application/docbook+xml",
      (char *)"application/x-kword",
      (char *)"application/x-kword-crypt",
      (char *)"application/x-lyx",
      (char *)"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
      (char *)"application/vnd.openxmlformats-officedocument.wordprocessingml.template",
      (char *)"application/wps-office.wps",
      (char *)"application/wps-office.wpt",
      (char *)"application/wps-office.doc",
      (char *)"application/wps-office.docx",
      (char *)"text/vnd.graphviz",
      NULL}},
    //演示文件
    {(char *)N_("Presentation"),
     {(char *)"application/vnd.ms-powerpoint",
      (char *)"application/vnd.openxmlformats-officedocument.presentationml.presentation",
      (char *)"application/vnd.sun.xml.impress",
      (char *)"application/vnd.oasis.opendocument.presentation",
      (char *)"application/x-magicpoint",
      (char *)"application/x-kpresenter",
      (char *)"application/wps-office.dps",
      (char *)"application/wps-office.dpt",
      (char *)"application/wps-office.pptx",
      (char *)"application/wps-office.ppt",
      (char *)"application/wps-office.pot",
      NULL}},
    //PDF文档
    {(char *)N_("PDF Documents"),
     {(char *)"application/pdf",
      (char *)"application/x-pdf",
      NULL}},
    //图形
    #if 0
    {(char *)N_("Illustration"),
     {(char *)"application/illustrator",
      (char *)"application/vnd.corel-draw",
      (char *)"application/vnd.stardivision.draw",
      (char *)"application/vnd.oasis.opendocument.graphics",
      (char *)"application/x-dia-diagram",
      (char *)"application/x-karbon",
      (char *)"application/x-killustrator",
      (char *)"application/x-kivio",
      (char *)"application/x-kontour",
      (char *)"application/x-wpg",
      NULL}},
    #endif
    //电子表格
    {(char *)N_("Spreadsheet"),
     {(char *)"application/vnd.lotus-1-2-3",
      (char *)"application/vnd.ms-excel",
      (char *)"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
      (char *)"application/vnd.stardivision.calc",
      (char *)"application/vnd.sun.xml.calc",
      (char *)"application/vnd.oasis.opendocument.spreadsheet",
      (char *)"application/x-applix-spreadsheet",
      (char *)"application/x-gnumeric",
      (char *)"application/x-kspread",
      (char *)"application/x-kspread-crypt",
      (char *)"application/x-quattropro",
      (char *)"application/x-sc",
      (char *)"application/x-siag",
      (char*)"application/wps-office.et",
      (char*)"application/wps-office.ett",
      (char*)"application/wps-office.xls",
      (char*)"application/wps-office.xlsx",
      (char*)"application/wps-office.xlt",
      NULL}}};

static void
type_add_custom_type (NemoQueryEditorRow *row,
                      const char *mime_type,
                      const char *description,
                      GtkTreeIter *iter)
{
    GtkTreeModel *model;
    GtkListStore *store;

    model = gtk_combo_box_get_model (GTK_COMBO_BOX (row->type_widget));
    store = GTK_LIST_STORE (model);

    gtk_list_store_append (store, iter);
    gtk_list_store_set (store, iter,
                        0, description,
                        2, mime_type,
                        -1);
}

static void
type_combo_changed (GtkComboBox *combo_box, NemoQueryEditorRow *row)
{
    GtkTreeIter iter;
    gboolean other;
    GtkTreeModel *model;

    if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX(row->type_widget),
                                        &iter))
    {
        return;
    }

    model = gtk_combo_box_get_model (GTK_COMBO_BOX(row->type_widget));
    gtk_tree_model_get (model, &iter, 3, &other, -1);

    if (other)
    {
        GList *mime_infos, *l;
        GtkWidget *dialog;
        GtkWidget *scrolled, *treeview;
        GtkListStore *store;
        GtkTreeViewColumn *column;
        GtkCellRenderer *renderer;
        GtkWidget *toplevel;
        GtkTreeSelection *selection;

        mime_infos = g_content_types_get_registered ();

        store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
        for (l = mime_infos; l != NULL; l = l->next)
        {
            GtkTreeIter it;
            char *mime_type = l->data;
            char *description;

            description = g_content_type_get_description (mime_type);
            if (description == NULL)
            {
                description = g_strdup (mime_type);
            }

            gtk_list_store_append (store, &it);
            gtk_list_store_set (store, &it,
                                0, description,
                                1, mime_type,
                                -1);

            g_free (mime_type);
            g_free (description);
        }
        g_list_free (mime_infos);

        toplevel = gtk_widget_get_toplevel (GTK_WIDGET(combo_box));
        dialog = gtk_dialog_new_with_buttons (_("Select type"),
                                              GTK_WINDOW (toplevel),
                                                           0,
                                              GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                              _("Select"), GTK_RESPONSE_OK,
                                              NULL);
        gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 600);

        scrolled = gtk_scrolled_window_new (NULL, NULL);
        gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
                                        GTK_POLICY_AUTOMATIC,
                                        GTK_POLICY_AUTOMATIC);
        gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
                                             GTK_SHADOW_IN);

        gtk_widget_show (scrolled);
        gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), scrolled, TRUE, TRUE, 6);

        treeview = gtk_tree_view_new ();
        gtk_tree_view_set_model (GTK_TREE_VIEW (treeview),
                                 GTK_TREE_MODEL (store));
        gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), 0,
                                              GTK_SORT_ASCENDING);

        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
        gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);

        renderer = gtk_cell_renderer_text_new ();
        column = gtk_tree_view_column_new_with_attributes ("Name",
                                                           renderer,
                                                           "text",
                                                           0,
                                                           NULL);
        gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
        gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);

        gtk_widget_show (treeview);
        gtk_container_add (GTK_CONTAINER (scrolled), treeview);

        if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
        {
            char *mimetype, *description;

            gtk_tree_selection_get_selected (selection, NULL, &iter);
            gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
                                0, &description,
                                1, &mimetype,
                                -1);

            type_add_custom_type (row, mimetype, description, &iter);
            gtk_combo_box_set_active_iter (GTK_COMBO_BOX (row->type_widget),
                                           &iter);
        }
        else
        {
            gtk_combo_box_set_active (GTK_COMBO_BOX (row->type_widget), 0);
        }

        gtk_widget_destroy (dialog);
    }

    nemo_query_editor_changed (row->editor);
}

static GtkWidget *
type_row_create_widgets (NemoQueryEditorRow *row)
{
    GtkWidget *combo;
    GtkCellRenderer *cell;
    GtkListStore *store;
    GtkTreeIter iter;
    guint i;

    store = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN);
    combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
    g_object_unref (store);

    cell = gtk_cell_renderer_text_new ();
    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
    gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell,
                                    "text", 0,
                                    NULL);
    gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo),
                                          type_separator_func,
                                          NULL, NULL);

    gtk_list_store_append (store, &iter);
    gtk_list_store_set (store, &iter, 0, _("Any"), -1);
    gtk_list_store_append (store, &iter);
    gtk_list_store_set (store, &iter, 0, "---", -1);

    for (i = 0; i < G_N_ELEMENTS (mime_type_groups); i++)
    {
        gtk_list_store_append (store, &iter);
        gtk_list_store_set (store, &iter,
                            0, gettext (mime_type_groups[i].name),
                            1, mime_type_groups[i].mimetypes,
                            -1);
    }

    gtk_list_store_append (store, &iter);
    gtk_list_store_set (store, &iter, 0, "---", -1);
    gtk_list_store_append (store, &iter);
    gtk_list_store_set (store, &iter, 0, _("Others"), 3, TRUE, -1);

    gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);

    g_signal_connect (combo, "changed",
                      G_CALLBACK(type_combo_changed),
                      row);

    gtk_widget_show (combo);

    gtk_box_pack_start (GTK_BOX (row->hbox), combo, FALSE, FALSE, 0);

    return combo;
}

static void
type_row_add_to_query (NemoQueryEditorRow *row,
                       NemoQuery *query)
{
    GtkTreeIter iter;
    char **mimetypes;
    char *mimetype;
    GtkTreeModel *model;

    if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (row->type_widget),
                                        &iter))
    {
        return;
    }

    model = gtk_combo_box_get_model (GTK_COMBO_BOX (row->type_widget));
    gtk_tree_model_get (model, &iter, 1, &mimetypes, 2, &mimetype, -1);

    if (mimetypes != NULL)
    {
        while (*mimetypes != NULL)
        {
            nemo_query_add_mime_type (query, *mimetypes);
            mimetypes++;
        }
    }
    if (mimetype)
    {
        nemo_query_add_mime_type (query, mimetype);
        g_free (mimetype);
    }
}

static gboolean
all_group_types_in_list (char **group_types, GList *mime_types)
{
    GList *l;
    char **group_type;
    char *mime_type;
    gboolean found;

    group_type = group_types;
    while (*group_type != NULL)
    {
        found = FALSE;

        for (l = mime_types; l != NULL; l = l->next)
        {
            mime_type = l->data;

            if (strcmp(mime_type, *group_type) == 0)
            {
                found = TRUE;
                break;
            }
        }

        if (!found)
        {
            return FALSE;
        }
        group_type++;
    }
    return TRUE;
}

static GList *
remove_group_types_from_list (char **group_types, GList *mime_types)
{
    GList *l, *next;
    char **group_type;
    char *mime_type;

    group_type = group_types;
    while (*group_type != NULL)
    {
        for (l = mime_types; l != NULL; l = next)
        {
            mime_type = l->data;
            next = l->next;

            if (strcmp(mime_type, *group_type) == 0)
            {
                mime_types = g_list_remove_link (mime_types, l);
                g_free (mime_type);
                break;
            }
        }

        group_type++;
    }
    return mime_types;
}

static void
type_add_rows_from_query (NemoQueryEditor *editor,
                          NemoQuery *query)
{
    GList *mime_types;
    char *mime_type;
    const char *desc;
    NemoQueryEditorRow *row;
    GtkTreeIter iter;
    guint i;
    GtkTreeModel *model;
    GList *l;

    mime_types = nemo_query_get_mime_types (query);

    if (mime_types == NULL)
    {
        return;
    }

    for (i = 0; i < G_N_ELEMENTS (mime_type_groups); i++)
    {
        if (all_group_types_in_list (mime_type_groups[i].mimetypes,
                                     mime_types))
        {
            mime_types = remove_group_types_from_list (mime_type_groups[i].mimetypes,
                                                       mime_types);

            row = nemo_query_editor_add_row (editor,
                                             NEMO_QUERY_EDITOR_ROW_TYPE);

            model = gtk_combo_box_get_model (GTK_COMBO_BOX (row->type_widget));

            gtk_tree_model_iter_nth_child (model, &iter, NULL, i + 2);
            gtk_combo_box_set_active_iter (GTK_COMBO_BOX(row->type_widget),
                                           &iter);
        }
    }

    for (l = mime_types; l != NULL; l = l->next)
    {
        mime_type = l->data;

        desc = g_content_type_get_description (mime_type);
        if (desc == NULL)
        {
            desc = mime_type;
        }

        row = nemo_query_editor_add_row (editor,
                                         NEMO_QUERY_EDITOR_ROW_TYPE);

        type_add_custom_type (row, mime_type, desc, &iter);
        gtk_combo_box_set_active_iter (GTK_COMBO_BOX (row->type_widget),
                                       &iter);
    }

    g_list_free_full (mime_types, g_free);
}

/* End of row types */

/* Modified Date */

static void
date_combo_changed (GtkComboBox *combo_box, NemoQueryEditorRow *row)
{
    GtkTreeIter iter;

    if (!GTK_IS_COMBO_BOX (row->type_widget))
    {
        return;
    }

    if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (row->type_widget), &iter))
    {
        return;
    }

    nemo_query_editor_changed (row->editor);
}

//获取任何的起始和终止时间
static void
get_any_date_times (GDateTime **initial_date, GDateTime **end_date)
{
    GDateTime *now;
    gint year, month, day;
    GTimeZone *time_zone;

    now = g_date_time_new_now_local ();
    g_date_time_get_ymd (now, &year, &month, &day);
    time_zone = g_time_zone_new_local ();
    *initial_date = g_date_time_new (time_zone, 1970, 1, 1, 0, 0, 0.0);
    *end_date = g_date_time_new (time_zone, year, month, day, 23, 59, 59.999);

    g_date_time_unref (now);
    g_time_zone_unref (time_zone);
}

//获取今天的起始和终止时间
static void
get_today_date_times (GDateTime **initial_date, GDateTime **end_date)
{
    GDateTime *now;
    gint year, month, day;
    GTimeZone *time_zone;

    now = g_date_time_new_now_local ();
    g_date_time_get_ymd (now, &year, &month, &day);
    time_zone = g_time_zone_new_local ();
    *initial_date = g_date_time_new (time_zone, year, month, day, 0, 0, 0.0);
    *end_date = g_date_time_new (time_zone, year, month, day, 23, 59, 59.999);

    g_date_time_unref (now);
    g_time_zone_unref (time_zone);
}

//获取本周的起始和终止时间
static void
get_this_week_date_times (GDateTime **initial_date, GDateTime **end_date)
{
    GDateTime *now;
    GDateTime *base;
    GDateTime *tmp;
    gint week_day;
    gint year, month, day;
    GTimeZone *time_zone;

    now = g_date_time_new_now_local ();
    g_date_time_get_ymd (now, &year, &month, &day);
    week_day = g_date_time_get_day_of_week (now);
    time_zone = g_time_zone_new_local ();
    base = g_date_time_new (time_zone, year, month, day, 0, 0, 0.0);

    switch (week_day)
    {
        case 1:
            *initial_date = g_date_time_add_days (base, 0);
            tmp = g_date_time_add_days (base, 6);
            *end_date = g_date_time_add_full (tmp, 0, 0, 0, 23, 59, 59.999);
            break;
        case 2:
            *initial_date = g_date_time_add_days (base, -1);
            tmp = g_date_time_add_days (base, 5);
            *end_date = g_date_time_add_full (tmp, 0, 0, 0, 23, 59, 59.999);
            break;
        case 3:
            *initial_date = g_date_time_add_days (base, -2);
            tmp = g_date_time_add_days (base, 4);
            *end_date = g_date_time_add_full (tmp, 0, 0, 0, 23, 59, 59.999);
            break;
        case 4:
            *initial_date = g_date_time_add_days (base, -3);
            tmp = g_date_time_add_days (base, 3);
            *end_date = g_date_time_add_full (tmp, 0, 0, 0, 23, 59, 59.999);
            break;
        case 5:
            *initial_date = g_date_time_add_days (base, -4);
            tmp = g_date_time_add_days (base, 2);
            *end_date = g_date_time_add_full (tmp, 0, 0, 0, 23, 59, 59.999);
            break;
        case 6:
            *initial_date = g_date_time_add_days (base, -5);
            tmp = g_date_time_add_days (base, 1);
            *end_date = g_date_time_add_full (tmp, 0, 0, 0, 23, 59, 59.999);
            break;
        case 7:
            *initial_date = g_date_time_add_days (base, -6);
            tmp = g_date_time_add_days (base, 0);
            *end_date = g_date_time_add_full (tmp, 0, 0, 0, 23, 59, 59.999);
            break;
    }

    g_date_time_unref (now);
    g_date_time_unref (base);
    g_date_time_unref (tmp);
    g_time_zone_unref (time_zone);
}

//获取本月的起始和终止时间
void get_this_month_date_times (GDateTime **initial_date, GDateTime **end_date)
{
    GTimeVal time;
    GDate *date;
    GDateYear date_year;
    GDateMonth date_month;
    gint year, month, days;
    GTimeZone *time_zone;

    g_get_current_time (&time);
    date = g_date_new ();
    g_date_set_time_val (date, &time);
    date_year = g_date_get_year (date);
    date_month = g_date_get_month (date);
    year = (gint) date_year;
    month = (gint) date_month;
    days = (gint) g_date_get_days_in_month (date_month, date_year);

    time_zone = g_time_zone_new_local ();

    *initial_date = g_date_time_new (time_zone, year, month, 1, 0, 0, 0.0);
    *end_date = g_date_time_new (time_zone, year, month, days, 23, 59, 59.999);

    g_date_free (date);
    g_time_zone_unref (time_zone);
}

//获取今年的起始和终止时间
void get_this_year_date_times (GDateTime **initial_date, GDateTime **end_date)
{
    GTimeVal time;
    GDate *date;
    GDateYear date_year;
    GDateMonth date_month;
    GDateDay date_day;
    gint year, month, day;
    GTimeZone *time_zone;

    g_get_current_time (&time);
    date = g_date_new ();
    g_date_set_time_val (date, &time);
    date_year = g_date_get_year (date);
    date_month = g_date_get_month (date);
    date_day = g_date_get_day (date);
    year = (gint) date_year;
    month = (gint) date_month;
    day = (gint) date_day;

    time_zone = g_time_zone_new_local ();

    *initial_date = g_date_time_new (time_zone, year, 1, 1, 0, 0, 0.0);
    *end_date = g_date_time_new (time_zone, year, month, day, 23, 59, 59.999);

    g_date_free (date);
    g_time_zone_unref (time_zone);
}

//获取今年以前的起始和终止时间
void get_before_this_year_date_times (GDateTime **initial_date, GDateTime **end_date)
{
    GTimeVal time;
    GDate *date;
    GDateYear date_year;
    gint year;
    GTimeZone *time_zone;

    g_get_current_time (&time);
    date = g_date_new ();
    g_date_set_time_val (date, &time);
    date_year = g_date_get_year (date);
    year = (gint) date_year;

    time_zone = g_time_zone_new_local ();

    *initial_date = g_date_time_new (time_zone, 1970, 1, 1, 0, 0, 0.0);
    *end_date = g_date_time_new (time_zone, year, 1, 1, 0, 0, 0.0);

    g_date_free (date);
    g_time_zone_unref (time_zone);
}

static GtkWidget *
modified_date_row_create_widgets (NemoQueryEditorRow *row)
{
    GtkListStore *store;
    GtkWidget *combo;
    GtkCellRenderer *cell;
    GtkTreeIter iter;
    GDateTime *initial_date;
    GDateTime *end_date;

    store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER);
    combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL(store));
    g_object_unref (store);

    cell = gtk_cell_renderer_text_new ();
    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
    gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell,
                                    "text", 0,
                                    NULL);

    gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo),
                                          type_separator_func,
                                          NULL, NULL);

    //“任何”选项
    gtk_list_store_append (store, &iter);
    get_any_date_times (&initial_date, &end_date);
    gtk_list_store_set (store, &iter, 0, _("Any"), 1, initial_date, 2, end_date, -1);

    //分隔线
    gtk_list_store_append (store, &iter);
    gtk_list_store_set (store, &iter, 0, "---", -1);

    //“今天”选项
    gtk_list_store_append (store, &iter);
    get_today_date_times (&initial_date, &end_date);
    gtk_list_store_set (store, &iter, 0, _("Today"), 1, initial_date, 2, end_date, -1);

    //“本周”选项
    gtk_list_store_append (store, &iter);
    get_this_week_date_times (&initial_date, &end_date);
    gtk_list_store_set (store, &iter, 0, _("This Week"), 1, initial_date, 2, end_date, -1);

    //“本月”选项
    gtk_list_store_append (store, &iter);
    get_this_month_date_times (&initial_date, &end_date);
    gtk_list_store_set (store, &iter, 0, _("This Month"), 1, initial_date, 2, end_date, -1);

    //“今年”选项
    gtk_list_store_append (store, &iter);
    get_this_year_date_times (&initial_date, &end_date);
    gtk_list_store_set (store, &iter, 0, _("This Year"), 1, initial_date, 2, end_date, -1);

    //“今年以前”选项
    gtk_list_store_append (store, &iter);
    get_before_this_year_date_times (&initial_date, &end_date);
    gtk_list_store_set (store, &iter, 0, _("Before This Year"), 1, initial_date, 2, end_date, -1);

    gtk_combo_box_set_active (GTK_COMBO_BOX(combo), 0);

    g_signal_connect (combo, "changed",
                      G_CALLBACK(date_combo_changed),
                      row);

    gtk_widget_show (combo);

    gtk_box_pack_start (GTK_BOX(row->hbox), combo, FALSE, FALSE, 0);

    return combo;
}

static void
modified_date_row_add_to_query (NemoQueryEditorRow *row,
                                NemoQuery *query)
{
    GtkTreeIter iter;
    GtkTreeModel *model;
    GDateTime *initial_date;
    GDateTime *end_date;
    GPtrArray *date_range;

    if (!GTK_IS_COMBO_BOX(row->type_widget))
    {
        return;
    }

    if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (row->type_widget), &iter))
    {
        return;
    }

    model = gtk_combo_box_get_model (GTK_COMBO_BOX (row->type_widget));
    gtk_tree_model_get (model, &iter, 1, &initial_date, 2, &end_date, -1);

    date_range = g_ptr_array_new ();
    g_ptr_array_add (date_range, initial_date);
    g_ptr_array_add (date_range, end_date);

    nemo_query_set_date_range (query, date_range);
}

static void
modified_date_add_rows_from_query (NemoQueryEditor *editor,
                                   NemoQuery *query)
{
    GPtrArray *date_range;
    NemoQueryEditorRow *row;
    GtkTreeIter iter;

    date_range = nemo_query_get_date_range (query);

    if (date_range == NULL)
    {
        return;
    }

    row = nemo_query_editor_add_row (editor, NEMO_QUERY_EDITOR_ROW_MODIFIED);
    gtk_combo_box_set_active_iter (GTK_COMBO_BOX (row->type_widget), &iter);
}

/*End of Modified Date*/

/* File Size */

static void
size_combo_changed (GtkComboBox *combo_box, NemoQueryEditorRow *row)
{
    GtkTreeIter iter;

    if (!GTK_IS_COMBO_BOX (row->type_widget))
    {
        return;
    }

    if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (row->type_widget), &iter))
    {
        return;
    }

    nemo_query_editor_changed (row->editor);
}

static GtkWidget *
size_row_create_widgets (NemoQueryEditorRow *row)
{
    GtkListStore *store;
    GtkWidget *combo;
    GtkCellRenderer *cell;
    GtkTreeIter iter;
    goffset initial_size;
    goffset end_size;

    store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER);
    combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL(store));
    g_object_unref (store);

    cell = gtk_cell_renderer_text_new ();
    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
    gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell,
                                    "text", 0,
                                    NULL);

    gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX(combo),
                                          type_separator_func,
                                          NULL, NULL);

    //“任何”选项
    gtk_list_store_append (store, &iter);
    initial_size = 0;
    end_size = G_MAXOFFSET;
    gtk_list_store_set (store, &iter, 0, _("Any"), 1, initial_size, 2, end_size, -1);

    //分隔线
    gtk_list_store_append (store, &iter);
    gtk_list_store_set (store, &iter, 0, "---", -1);

    //“0~100KB”，size单位为字节
    gtk_list_store_append (store, &iter);
    gtk_list_store_set (store, &iter, 0, _("0~100KB"), -1);
    initial_size = 0;
    end_size = 100000;
    gtk_list_store_set (store, &iter, 1, initial_size, 2, end_size, -1);

    //“100KB~500KB”
    gtk_list_store_append (store, &iter);
    gtk_list_store_set (store, &iter, 0, _("100KB~500KB"), -1);
    initial_size = 100000;
    end_size = 500000;
    gtk_list_store_set (store, &iter, 1, initial_size, 2, end_size, -1);

    //“500KB~1MB”
    gtk_list_store_append (store, &iter);
    gtk_list_store_set (store, &iter, 0, _("500KB~1MB"), -1);
    initial_size = 500000;
    end_size = 1000000;
    gtk_list_store_set (store, &iter, 1, initial_size, 2, end_size, -1);

    //“1MB~50MB”
    gtk_list_store_append (store, &iter);
    gtk_list_store_set (store, &iter, 0, _("1MB~50MB"), -1);
    initial_size = 1000000;
    end_size = 50000000;
    gtk_list_store_set (store, &iter, 1, initial_size, 2, end_size, -1);

    //“50MB~100MB”
    gtk_list_store_append (store, &iter);
    gtk_list_store_set (store, &iter, 0, _("50MB~100MB"), -1);
    initial_size = 50000000;
    end_size = 100000000;
    gtk_list_store_set (store, &iter, 1, initial_size, 2, end_size, -1);

    //“100MB~500MB”
    gtk_list_store_append (store, &iter);
    gtk_list_store_set (store, &iter, 0, _("100MB~500MB"), -1);
    initial_size = 100000000;
    end_size = 500000000;
    gtk_list_store_set (store, &iter, 1, initial_size, 2, end_size, -1);

    //“500MB~1GB”
    gtk_list_store_append (store, &iter);
    gtk_list_store_set (store, &iter, 0, _("500MB~1GB"), -1);
    initial_size = 500000000;
    end_size = 1000000000;
    gtk_list_store_set (store, &iter, 1, initial_size, 2, end_size, -1);

    //“超过1GB”
    gtk_list_store_append (store, &iter);
    gtk_list_store_set (store, &iter, 0, _("1GB~"), -1);
    initial_size = 1000000000;
    end_size = G_MAXOFFSET;
    gtk_list_store_set (store, &iter, 1, initial_size, 2, end_size, -1);

    gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);

    g_signal_connect (combo, "changed",
                      G_CALLBACK(size_combo_changed),
                      row);

    gtk_widget_show (combo);

    gtk_box_pack_start (GTK_BOX (row->hbox), combo, FALSE, FALSE, 0);

    return combo;
}

static void
size_row_add_to_query (NemoQueryEditorRow *row,
                       NemoQuery *query)
{
    GtkTreeIter iter;
    GtkTreeModel *model;
    goffset initial_size;
    goffset end_size;
    GPtrArray *size_range;

    if (!GTK_IS_COMBO_BOX (row->type_widget))
    {
        return;
    }

    if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (row->type_widget), &iter))
    {
        return;
    }

    model = gtk_combo_box_get_model (GTK_COMBO_BOX(row->type_widget));
    gtk_tree_model_get (model, &iter, 1, &initial_size, 2, &end_size, -1);

    size_range = g_ptr_array_new ();
    g_ptr_array_add (size_range, (gpointer) initial_size);
    g_ptr_array_add (size_range, (gpointer) end_size);

    nemo_query_set_size_range (query, size_range);
}

static void
size_add_rows_from_query (NemoQueryEditor *editor,
                          NemoQuery *query)
{
    GPtrArray *size_range;
    NemoQueryEditorRow *row;
    GtkTreeIter iter;

    size_range = nemo_query_get_date_range (query);

    if (size_range == NULL)
    {
        return;
    }

    row = nemo_query_editor_add_row (editor, NEMO_QUERY_EDITOR_ROW_SIZE);
    gtk_combo_box_set_active_iter (GTK_COMBO_BOX (row->type_widget), &iter);
}

/*End of File Size*/

static void
create_type_widgets (NemoQueryEditorRow *row)
{
    row->type_widget = row_type[row->type].create_widgets(row);
}

static NemoQueryEditorRow *
nemo_query_editor_add_row (NemoQueryEditor *editor,
                           NemoQueryEditorRowType type)
{
    GtkWidget *hbox, *label;
    NemoQueryEditorRow *row;

    hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
    gtk_widget_show (hbox);
    gtk_box_pack_start (GTK_BOX (editor->priv->vbox), hbox, FALSE, FALSE, 0);

    row = g_new0 (NemoQueryEditorRow, 1);
    row->editor = editor;
    row->type = type;
    row->hbox = hbox;

    label = gtk_label_new (NULL);
    row->label = label;
    gtk_label_set_text (GTK_LABEL (label), gettext (row_type[type].name));
    gtk_widget_show (label);
    gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);

    create_type_widgets (row);

    editor->priv->rows = g_list_append (editor->priv->rows, row);

    return row;
}

static void
select_folder_response_cb (GtkDialog *dialog,
                           gint response,
                           gpointer user_data)
{
    NemoQueryEditor *editor;
    GFile *file;

    editor = NEMO_QUERY_EDITOR (user_data);

    switch (response)
    {
    case GTK_RESPONSE_OK:
        file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
        nemo_query_editor_set_location (editor, file);
        nemo_query_editor_changed (editor);
        break;
    case GTK_RESPONSE_CANCEL:
    default:
        break;
    }

    gtk_widget_destroy (GTK_WIDGET (dialog));
}

static void
on_current_button_clicked (GtkButton *button,
                           gpointer user_data)
{
    NemoQueryEditor *editor;
    GtkWidget *dialog;
    GtkWidget *toplevel;

    editor = NEMO_QUERY_EDITOR (user_data);

    toplevel = gtk_widget_get_toplevel (GTK_WIDGET (button));
    dialog = gtk_file_chooser_dialog_new (_("Select Target Folder For Search"),
                                          GTK_WINDOW (toplevel),
                                          GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
                                          GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                          GTK_STOCK_OK, GTK_RESPONSE_OK,
                                          NULL);

    gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 600);

    g_signal_connect (dialog, "response",
                      G_CALLBACK(select_folder_response_cb), editor);

    gtk_widget_show (dialog);
}

static void
create_condition_row (NemoQueryEditor *editor,
                      GtkWidget *condition_hbox)
{
    GtkWidget *label;
    GtkWidget *search_current_label;
    GtkWidget *image;
    NemoQueryEditorRow *row;
    NemoQueryEditorRowType type;
    NemoQueryEditorPrivate *priv;

    priv = editor->priv;

    search_current_label = gtk_label_new (_("Search Scope:"));
    gtk_widget_show (search_current_label);
    gtk_box_pack_start (GTK_BOX (condition_hbox), search_current_label, FALSE, FALSE, 0);

    priv->search_current_button = gtk_button_new_with_label (_("Current"));
    gtk_widget_show (priv->search_current_button);
    gtk_box_pack_start (GTK_BOX (condition_hbox), priv->search_current_button, FALSE, FALSE, 0);

    image = gtk_image_new ();
    gtk_button_set_image (GTK_BUTTON (priv->search_current_button), image);
    gtk_button_set_always_show_image (GTK_BUTTON (priv->search_current_button), TRUE);

    g_signal_connect (priv->search_current_button, "clicked",
                      G_CALLBACK(on_current_button_clicked), editor);

    for (type = 0; type < NEMO_QUERY_EDITOR_ROW_LAST; type++)
    {
        row = g_new0 (NemoQueryEditorRow, 1);
        row->editor = editor;
        row->type = type;
        row->hbox = condition_hbox;

        label = gtk_label_new (NULL);
        row->label = label;
        gtk_label_set_text (GTK_LABEL (label), gettext (row_type[type].name));
        gtk_widget_show (label);
        gtk_box_pack_start (GTK_BOX(condition_hbox), label, FALSE, FALSE, 0);

        create_type_widgets (row);

        editor->priv->rows = g_list_append (editor->priv->rows, row);
    }
}

static void
nemo_query_editor_init (NemoQueryEditor *editor)
{
    NemoQueryEditorPrivate *priv;

    editor->priv = G_TYPE_INSTANCE_GET_PRIVATE (editor,
                                                NEMO_TYPE_QUERY_EDITOR,
                                                NemoQueryEditorPrivate);
    priv = editor->priv;

    priv->menu = NULL;

    priv->histories = g_settings_get_strv (nemo_preferences,
                                           NEMO_PREFERENCES_SAVED_SEARCHES);

    gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (editor)),
                                 GTK_STYLE_CLASS_TOOLBAR);
    gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (editor)),
                                 GTK_STYLE_CLASS_PRIMARY_TOOLBAR);

    gtk_orientable_set_orientation (GTK_ORIENTABLE (editor), GTK_ORIENTATION_VERTICAL);

    priv->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
    gtk_container_set_border_width (GTK_CONTAINER (priv->vbox), 6);
    gtk_box_pack_start (GTK_BOX (editor), priv->vbox,
                        FALSE, FALSE, 0);
    gtk_widget_show (priv->vbox);

    g_signal_connect (nemo_preferences,
                      "changed::" NEMO_PREFERENCES_SAVED_SEARCHES,
                      G_CALLBACK (on_saved_searches_setting_changed),
                      editor);
}

static void
on_menu_item_activated (GtkMenuItem *item,
                        gpointer     user_data)
{
    NemoQueryEditor *editor;
    NemoQuery *query;
    const gchar *history_entry;

    editor = NEMO_QUERY_EDITOR (user_data);

    gtk_menu_popdown (GTK_MENU (editor->priv->menu));

    if (!nemo_history_menu_item_is_clear_all (NEMO_HISTORY_MENU_ITEM (item))) {
        history_entry = g_object_get_data (G_OBJECT (item),
                                        "history-entry");

        //更新列表，从搜索记录中填充的搜索文本放置到列表第一位
        add_key_to_histories (editor, history_entry);

        query = nemo_query_new ();

        nemo_query_set_location (query, editor->priv->current_uri);
        nemo_query_set_text (query, history_entry);

        nemo_query_editor_set_query (editor, query);
        gtk_editable_set_position (GTK_EDITABLE ((editor->priv->entry)), -1);
        nemo_query_editor_changed (editor);

        g_object_unref (query);
    } else {
        g_settings_set_strv (nemo_preferences, NEMO_PREFERENCES_SAVED_SEARCHES, NULL);
    }
}

static void
on_menu_item_closed (NemoHistoryMenuItem *item, 
                     gpointer user_data)
{
    NemoQueryEditor *editor;
    const gchar *history_entry;
    gint i;
    GList *children;
    guint child_num;
    guint real_item_num;
    GtkWidget *new_item;
    guint length;

    editor = NEMO_QUERY_EDITOR (user_data);

    history_entry = g_object_get_data (G_OBJECT (item),
                                       "history-entry");

    g_signal_handlers_disconnect_by_func (item, 
                                          on_menu_item_activated, 
                                          editor);
    g_signal_handlers_disconnect_by_func (item, 
                                          on_menu_item_closed, 
                                          editor);
    gtk_widget_destroy (GTK_WIDGET (item));

    remove_key_from_histories (editor, history_entry);

    length = g_strv_length (editor->priv->histories);
    if (length == 0) {
        gtk_menu_popdown (GTK_MENU (editor->priv->menu));
    }

    //搜索记录不足10条时自动补全
    children = gtk_container_get_children (GTK_CONTAINER (editor->priv->menu));
    child_num = g_list_length (children);
    real_item_num = child_num - 2;

    if (real_item_num < 10 && length >= 10) {
        for (i = 0; i < 10 - real_item_num; i++) {
            new_item = nemo_history_menu_item_new (editor->priv->histories[real_item_num + i], FALSE);

            g_object_set_data_full (G_OBJECT (new_item),
                                "history-entry",
                                g_strdup (editor->priv->histories[real_item_num + i]),
                                (GDestroyNotify) g_free);

            gtk_menu_shell_insert (GTK_MENU_SHELL (editor->priv->menu), 
                                   new_item, real_item_num + i);

            g_signal_connect (new_item,
                              "activate",
                              G_CALLBACK (on_menu_item_activated),
                              editor);

            g_signal_connect (new_item,
                              "close",
                              G_CALLBACK (on_menu_item_closed),
                              editor);
        }
    }
}

static void
popup_histories_menu (NemoQueryEditor *editor, 
                      GdkEvent  *event)
{
    GtkWidget *menu, *item, *separator;
    gchar **histories;
    gint i;
    gint width;
    gint count = 0;

    gtk_widget_grab_focus(editor->priv->entry);

    if (g_strv_length (editor->priv->histories) == 0) {
        return;
    }

    g_clear_object (&editor->priv->menu);
    editor->priv->menu = menu = g_object_ref_sink (gtk_menu_new ());
    width = gtk_widget_get_allocated_width (editor->priv->entry);
    gtk_widget_set_size_request (menu, width, -1);

    histories = editor->priv->histories;

    for (i = 0; i < g_strv_length (histories); i++) {
        ++count;
        //只显示10条记录
        if (count > 10)
            break;

        item = nemo_history_menu_item_new (histories[i], FALSE);

        g_object_set_data_full (G_OBJECT (item),
                                "history-entry",
                                g_strdup (histories[i]),
                                (GDestroyNotify) g_free);

        gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);

        g_signal_connect (item,
                          "activate",
                          G_CALLBACK (on_menu_item_activated),
                          editor);

        g_signal_connect (item,
                          "close",
                          G_CALLBACK (on_menu_item_closed),
                          editor);
    }

    separator = gtk_separator_menu_item_new ();
    gtk_widget_show (separator);
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), separator);

    //删除全部搜索历史
    item = nemo_history_menu_item_new (_("Delete all search histories"), TRUE);
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);

    g_signal_connect (item,
                      "activate",
                      G_CALLBACK (on_menu_item_activated),
                      editor);

    gtk_menu_popup_at_widget (GTK_MENU (menu), 
                              editor->priv->entry, 
                              GDK_GRAVITY_SOUTH_WEST, 
                              GDK_GRAVITY_NORTH_WEST, 
                              event);
}

static gboolean
on_button_press_event (GtkWidget *widget, 
                       GdkEvent  *event, 
                       gpointer user_data)
{
    NemoQueryEditor *editor;

    editor = NEMO_QUERY_EDITOR (user_data);

    g_autofree gchar *text = NULL;
    text = get_sanitized_query_string (editor);
    if (strlen (text) == 0) {
        if ((event->button.state & gtk_accelerator_get_default_mod_mask ()) == 0 && event->button.button == 1) {

            popup_histories_menu (editor, event);

        }
        return GDK_EVENT_STOP;
    }

    return GDK_EVENT_PROPAGATE;
}

static void
setup_widgets (NemoQueryEditor *editor, 
               GtkWidget *entry)
{
    GtkWidget *entry_hbox;
    GtkWidget *condition_hbox;

    /* Create visible part: */
    entry_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
    gtk_widget_show (entry_hbox);
    gtk_box_pack_start (GTK_BOX (editor->priv->vbox), entry_hbox, FALSE, FALSE, 0);

    editor->priv->entry = entry;

    g_signal_connect (editor->priv->entry, "activate",
                      G_CALLBACK(entry_activate_cb), editor);

    g_signal_connect (editor->priv->entry, "changed",
                      G_CALLBACK(entry_changed_cb), editor);

    g_signal_connect(editor->priv->entry, "button-press-event",
                     G_CALLBACK(on_button_press_event), editor);

    condition_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
    gtk_widget_show (condition_hbox);
    gtk_box_pack_start (GTK_BOX (editor->priv->vbox), condition_hbox, FALSE, FALSE, 0);

    create_condition_row (editor, condition_hbox);

    nemo_query_editor_changed (editor);
}

static void
nemo_query_editor_changed_force (NemoQueryEditor *editor, gboolean force_reload)
{
    NemoQuery *query;

    if (editor->priv->change_frozen) {
        return;
    }

    query = nemo_query_editor_get_query (editor);
    g_signal_emit (editor, signals[CHANGED], 0,
                   query, force_reload);
    g_clear_object (&query);
}

static void
nemo_query_editor_changed (NemoQueryEditor *editor)
{
    nemo_query_editor_changed_force (editor, TRUE);
}

static void
add_location_to_query (NemoQueryEditor *editor,
                       NemoQuery *query)
{
    char *uri;

    uri = g_strdup (editor->priv->current_uri);

    nemo_query_set_location (query, uri);
    g_free (uri);
}

NemoQuery *
nemo_query_editor_get_query (NemoQueryEditor *editor)
{
    NemoQuery *query;
    g_autofree gchar *query_text = NULL;
    GList *l;
    NemoQueryEditorRow *row;

    if (editor == NULL || editor->priv == NULL || editor->priv->entry == NULL) {
        return NULL;
    }

    query_text = get_sanitized_query_string (editor);

    query = nemo_query_new ();
    nemo_query_set_text (query, query_text);

    add_location_to_query (editor, query);

    for (l = editor->priv->rows; l != NULL; l = l->next) {
        row = l->data;

        row_type[row->type].add_to_query(row, query);
    }

    return query;
}

GtkWidget *
nemo_query_editor_new (GtkWidget *entry)
{
    GtkWidget *editor;

    editor = g_object_new (NEMO_TYPE_QUERY_EDITOR, NULL);
    setup_widgets (NEMO_QUERY_EDITOR (editor), entry);

    return editor;
}

static void
update_location (NemoQueryEditor *editor)
{
    NemoFile *file;
    char *file_name;
    GtkWidget *image;
    char *icon_name;

    file = nemo_file_get_by_uri (editor->priv->current_uri);

    if (file != NULL)
    {
        if (nemo_file_is_home (file))
        {
            file_name = g_strdup (_("Home"));
        }
        else
        {
            file_name = nemo_file_get_display_name (file);
        }
        gtk_button_set_label (GTK_BUTTON (editor->priv->search_current_button), file_name);
        g_free (file_name);

        image = gtk_button_get_image (GTK_BUTTON (editor->priv->search_current_button));
        icon_name = nemo_file_get_control_icon_name (file);
        gtk_image_set_from_icon_name (GTK_IMAGE (image), icon_name, GTK_ICON_SIZE_MENU);

        g_free (icon_name);
        nemo_file_unref (file);
    }
}

void nemo_query_editor_set_location (NemoQueryEditor *editor,
                                     GFile *location)
{
    g_free (editor->priv->current_uri);
    editor->priv->current_uri = g_file_get_uri (location);
    update_location (editor);
}

void nemo_query_editor_set_query (NemoQueryEditor *editor,
                                  NemoQuery *query)
{
    NemoQueryEditorRowType type;
    char *text = NULL;

    if (query != NULL) {
        text = nemo_query_get_text (query);
    }

    if (!text) {
        text = g_strdup ("");
    }

    editor->priv->change_frozen = TRUE;
    gtk_entry_set_text (GTK_ENTRY (editor->priv->entry), text);
    gtk_widget_grab_focus (editor->priv->entry);

    g_free (editor->priv->current_uri);
    editor->priv->current_uri = NULL;

    if (query != NULL) {
        editor->priv->current_uri = nemo_query_get_location (query);
        update_location (editor);

        for (type = 0; type < NEMO_QUERY_EDITOR_ROW_LAST; type++) {
            row_type[type].add_rows_from_query(editor, query);
        }
    }

    g_free (editor->priv->last_set_query_text);
    editor->priv->last_set_query_text = text;

    editor->priv->change_frozen = FALSE;
}
